
/**
 * @class Ext.ux.FilterPanel
 * @extends Ext.Panel
 * Renders a filter panel suitable for attachment to a Kohive.Window via a WindowSidePanel
 * 
 * @cfg filters
 * @type Array
 * An array of filter configuration objects. This should contain at least the following:
 * <ul>
 *   <li>title: The title of the filter, rendered as a heading</li>
 *   <li>iconCls: The CSS class used to render the icon for items in this filter</li>
 *   <li>store: The Ext.data.Store subclass to retrieve data from</li>
 *   <li>convert: A function used with store.each to retrieve the values from the store</li>
 * </ul>
 * Defaults to an empty array
 */
Ext.ux.FilterPanel = Ext.extend(Ext.Panel, {

	constructor			: function(config) {
		config = config || {};

		Ext.applyIf(config, {
					filters		: [],

					baseCls		: 'x-window-side-panel',
					border		: false,
					header		: false,
					defaults	: {
						border	: false
					}
				});

		Ext.ux.FilterPanel.superclass.constructor.call(this, config);

		Ext.apply(this, {
					/**
					 * @property currentlyApplied
					 * @type Object
					 * The valueField values of all currently ticked filter items, split by filter.
					 * e.g. {'tags': ['cat, 'dog'], 'authors': [1, 5, 8]}
					 */
					currentlyApplied	: {}
				});

		this.initEvents();
		this.setupFilters();
		this.initListeners();
	},

	/**
	 * Renders the filters into the panel's body
	 */
	renderFilters		: function() {
		this.template.overwrite(this.body, {
					filters	: this.filters
				});

		// re-tick boxes as appropriate after rendering
		for (var name in this.currentlyApplied) {
			this.setCurrentlyApplied(name, this.currentlyApplied[name], false);
		}
	},

	/**
	 * Instantiates the filter config objects into Ext.ux.Filter subclass objects
	 */
	setupFilters		: function() {
		var filters = [];

		Ext.each(this.filters, function(filterConfig) {
					filters.push(Ext.ux.FilterManager.build(filterConfig.ftype,
							filterConfig
					));
				}, this);

		this.filters = filters;
	},

	/**
	 * Returns all currently applied (ticked) filter items
	 * @return {Object} All currently applied filter items
	 */
	getCurrentlyApplied	: function() {
		return this.currentlyApplied;
	},

	/**
	 * @property template
	 * @type Ext.XTemplate
	 * The XTemplate used to create each filter
	 */
	template			: new Ext.XTemplate(
			'<ul class="filters many">',
			'<tpl for="filters">',
			'<li<tpl if="this.isEmpty(options)"> class="x-empty"</tpl>>',
			'<h3>{title}</h3>',
			'<ul class="filter-options none" id="filter-options-{name}">',
			'<tpl for="options">',
			'<tpl if="text">',
			'<li class="filter-option {iconCls}<tpl if="!iconCls">{parent.name}</tpl>" id="filter-option-{parent.name}-{id}">',
			'<div class="x-icon"></div>',
			'<div class="filter-options-hidden"></div>',
			'<span class="text">{text}</span>', '</li>', '</tpl>', '</tpl>',
			'</ul>', '</li>', '</tpl>', '</ul>', {
				isEmpty	: function(options) {
					if (!options || !options.length)
						return true;
					if (options.length == 0)
						return true;
					return false;
				}
			}
	),

	/**
	 * Called when the body element is clicked - fires events appropriately
	 */
	onClick				: function(e, element) {
		var el = Ext.get(element), liEl = e.getTarget('li.filter-option', 2), h3El = e
				.getTarget('h3', 2), inputEl = e.getTarget('div', 2);
		spanEl = e.getTarget('span', 2);

		switch (element) {
			case liEl :
				// case spanEl: this.onLiClick(liEl); break;
			case spanEl :
				this.onInputClick(el);
				break;
			case h3El :
				this.onHeadingClick(el);
				break;
			case inputEl :
				this.onInputClick(el);
				break;
		}
	},

	/**
	 * Called when the user mouses over the body.  Adds a 'hover' class to the appropriate <li>
	 * @param {Ext.EventObject} e The Event Object
	 */
	onMouseover			: function(e) {
		var li = Ext.get(e.getTarget('li.filter-option', 2));

		if (li)
			li.addClass('x-over');
	},

	/**
	 * Called when the mouse leaves the body element.  Removes class 'hover' from all <li> elements
	 * @param {Ext.EventObject} e The Event object
	 */
	onMouseout			: function(e) {
		var ul = this.body.select('li').removeClass('x-over');
	},

	/**
	 * Called when a heading element is clicked.  Default behaviour is to collapse the associated ul
	 * @param {Ext.Element} heading The Ext.Element for the heading that was clicked
	 */
	onHeadingClick		: function(heading) {
		var animConfig = {
			scope		: this,
			remove		: false,
			duration	: 0.3,
			callback	: function() {
				// create a closure to keep a reference to the correct heading
				return function() {
					heading.toggleClass('heading-collapsed');
				};
			}()
		};

		var ul = heading.parent().child('ul'), action = ul.isVisible()
				? 'slideOut'
				: 'slideIn';

		ul.enableDisplayMode();
		ul[action]('t', animConfig);
	},

	/**
	 * Called when an <li> element is clicked.  Default behaviour is to mark the filter item this
	 * represents as the only one applied, and to untick all others
	 * @param {Ext.Element} li The <li> element that was clicked
	 */
	onLiClick			: function(li) {
		var options = this.parseFilterId(li.id), app = this.currentlyApplied[options.name]
				|| [];

		// reset options of all types, max. one option can be selected
		this.currentlyApplied = {};

		this.setCurrentlyApplied(options.name, [options.id]);
	},

	/**
	 * Called when a checkbox is clicked.  By default this marks the item the checkbox represents
	 * as being applied, but does not un-mark any other items
	 * @param {Ext.Element} input The checkbox that was clicked
	 */
	onInputClick		: function(input) {
		var options = this.parseFilterId(input.parent().id), app = this.currentlyApplied[options.name]
				|| [];

		// check if the option has an id
		if (!options.id)
			return;

		app.indexOf(options.id) == -1 ? app.push(options.id) : app
				.remove(options.id);

		this.setCurrentlyApplied(options.name, app);
	},

	/**
	 * Sets the list of currently applied items.
	 * Ticks or unticks the appropriate checkboxes
	 * @param {String} name The name of the filter to set applied IDs for
	 * @param {Array} ids The ids of the items currently applied
	 * @param {Boolean} firesEvent False to cancel firing on the filters-updated event (defaults to true)
	 */
	setCurrentlyApplied	: function(name, ids, firesEvent) {
		var numOfCurrentlyApplied = 0;

		this.currentlyApplied[name] = ids || [];

		// loop through all filter options of all types
		this.el.select('li.filter-option').each(function(liEl) {
			var filterOption = this.parseFilterId(liEl.dom.id);

			// remove selection marker from filter option
			liEl.removeClass('x-selected');
			liEl.child('div').removeClass('visible');

			// add selection marker to filter option if currently selected
			if (this.currentlyApplied[filterOption.name] != undefined
					&& this.currentlyApplied[filterOption.name]
							.indexOf(filterOption.id) >= 0) {
				liEl.addClass('x-selected');
				liEl.child('div').addClass('visible');
				numOfCurrentlyApplied++;
			}
		}, this
		);

		// add class to the parent ul of all filter types depending on the amount of selected items
		// if (numOfCurrentlyApplied > 1)
		// this.el.child('ul.filters').removeClass('many');
		// this.el.child('ul.filters').addClass('many');
		// else
		// this.el.child('ul.filters').removeClass('many');

		// loop through each of the filters and check if any are empty
		for (filter in this.currentlyApplied) {
			if (this.currentlyApplied[filter].length == 0)
				delete this.currentlyApplied[filter];
		};

		if (firesEvent !== false)
			this.fireEvent('filters-updated', this.getCurrentlyApplied(),
					(name == "hashs") ? true : false
			);
	},

	/**
	 * Parses the ID string of a filter <li> node into a filter name and item ID. This is used
	 * to parse name and ID from a <li> element inside this.template
	 * @param {String} id The ID of the filter element that was clicked
	 * @return {Object} An object containing the filter name and id of the filtered item
	 */
	parseFilterId		: function(id) {
		var els = id.split('-'), name = els[2].split('.')[0];

		return {
			name	: name,
			id		: (els[4]) ? els[3] + "-" + els[4] : els[3]
		};
	},

	/**
	 * Checks the width of the text of all child span.text elements and adds ellipsis to them
	 * iff nessecary
	 */
	initEllipsis		: function() {
		var maxWidth = 70;

		// Get each of the elements
		var els = this.el.select('span.text');

		// Loop through each of the span childs
		els.each(function(el) {
					var text = el.dom.innerHTML;
					var size = Ext.util.TextMetrics.measure(el, text);
					var gotIt = false;

					if (size.width > maxWidth) {
						for (var i = 0; i < text.length; i++) {
							if (gotIt)
								return;

							var newString = text.slice(0, text.length - i)
									+ '...';
							var newSize = Ext.util.TextMetrics.measure(el,
									newString
							);

							if (maxWidth > newSize.width) {
								gotIt = true;

								el.dom.innerHTML = newString;
							};
						};
					};
				}, this);
	},

	/**
	 * Listens to load events for each filter's store and refreshes the filter accordingly
	 */
	initListeners		: function() {
		Ext.each(this.filters, function(filter) {
					filter.on('refreshed', this.renderFilters, this);
					filter.on('refreshed', this.initEllipsis, this);
				}, this);

		this.on('refreshed', this.renderFilters, this);
		this.on('refreshed', this.initEllipsis, this);

		// attach click listeners once the panel has been rendered
		this.on('render', function() {
					this.renderFilters();

					this.body.on({
								scope		: this,
								click		: this.onClick,
								mouseover	: this.onMouseover,
								mouseout	: this.onMouseout
							});
				}, this);
	},

	/**
	 * Sets up events emitted by this component
	 */
	initEvents			: function() {
		this.addEvents(
				/**
				 * @event filters-updated
				 * Fired whenever the user applies a new filter option
				 * @param {Object} currentlyApplied An object where the keys are the filter names and each
				 * value is an array of the IDs currently selected
				 */
				'filters-updated'
		);
	}
}
);

Ext.reg('filter_panel', Ext.ux.FilterPanel);
